/**
 * MinesweeperFlags.java
 * @author Vincent Dominguez
 * 
 * The main game activity for the Minesweeper Flags application
 * Contains the game board, score, etc.
 */

package com.vmd.MSFlags;

import java.util.HashSet;
import java.util.Random;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class MinesweeperFlags extends Activity implements OnItemClickListener {
	/* Private Fields */
	private static enum MenuItems { MENU_SOUND }
	private static enum AdjacentPositions { TOP_LEFT, TOP, TOP_RIGHT, RIGHT,
											BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, LEFT }
	
	// finals
	private static final int SIDE_SZ = 16;
	private static final int NUM_FLAGS = 51;
	private static final int WIN_SCORE = 26;

	// fields related to game score
	private int mP1Score;
	private int mP2Score;
	private boolean mP1Turn;
	// fields related to game display
	private SquareAdapter<Square> adapter;
	private TextView mP1NameLabel;
	private TextView mP2NameLabel;
	private TextView mP1ScoreLabel;
	private TextView mP2ScoreLabel;
	private LinearLayout mP1Bkgd;
	private LinearLayout mP2Bkgd;
	// fields related to audio
	private MediaPlayer foundClip;
	private MediaPlayer missClip;
	private MediaPlayer winClip;
	private boolean soundOn;
	
	private Square[] squares;
	
	/* Overridden Life Cycle Methods */
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);	
    	setContentView(R.layout.game);
    	
    	// initialize game-specific state
    	mP1Turn = true;
    	mP1Score = 0;
    	mP2Score = 0;
    	
    	// initialize player labels
    	mP1NameLabel = (TextView) findViewById(R.id.game_p1name);
    	mP2NameLabel = (TextView) findViewById(R.id.game_p2name);
    	mP1ScoreLabel = (TextView) findViewById(R.id.game_p1score);
    	mP2ScoreLabel = (TextView) findViewById(R.id.game_p2score);
    	mP1Bkgd = (LinearLayout) findViewById(R.id.game_p1bkgd);
    	mP2Bkgd = (LinearLayout) findViewById(R.id.game_p2bkgd);
    	mP1ScoreLabel.setText("" + mP1Score);
    	mP2ScoreLabel.setText("" + mP2Score);
    	
    	// initialize sound setting
    	Bundle extras = getIntent().getExtras();
    	if (extras == null) {
    		SharedPreferences prefs = getSharedPreferences(getString(R.string.preferences_file), MODE_PRIVATE);
    		soundOn = prefs.getBoolean(getString(R.string.sound), true);
    	} else {
    		soundOn = extras.getBoolean(getString(R.string.sound), true);
    	}
    	
    	initializeFlags();
    	
    	adapter = new SquareAdapter<Square>(this, R.layout.grid_item, squares);
    	((GridView) findViewById(R.id.game_grid)).setAdapter(adapter);
    	((GridView) findViewById(R.id.game_grid)).setOnItemClickListener(this);
     }
	
    @Override
    public void onPause() {
    	super.onPause();
    	
    	// release media player resources
    	foundClip.release();
    	missClip.release();
    	// save sound setting
    	Editor editPrefs = getSharedPreferences(getString(R.string.preferences_file), MODE_PRIVATE).edit();
    	editPrefs.putBoolean(getString(R.string.sound), soundOn);
    	editPrefs.commit();
    }
    
    @Override
    public void onResume() {
    	super.onResume();
    	
    	// initialize media player
    	foundClip = MediaPlayer.create(this, R.raw.found);
    	missClip = MediaPlayer.create(this, R.raw.miss);
    }
	
	@Override
	public void finish() {
		Intent i = new Intent();
		i.putExtra(getString(R.string.sound), soundOn);
		setResult(RESULT_OK, i);
		super.finish();
	}
    
	/* UI Handlers */
	@Override
	public void onItemClick(AdapterView<?> a, View v, int position, long id) {
		Square selectedSquare = squares[position];
		
		// if the selected square was already flipped, do nothing
		if (selectedSquare.isFlipped()) {
			return;
		}
		
		// if it is a flag square, increment the current player's score
		if (selectedSquare instanceof FlagSquare) {
			handleFlagSquareClicked(selectedSquare);
		} else {
			handleRegularSquareClicked((position / SIDE_SZ), (position % SIDE_SZ));
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		menu.add(0, MenuItems.MENU_SOUND.ordinal(), 0, R.string.sound);
		return true;
	}
	
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		// Update the sound menu item to display whether sound is on or off
		MenuItem soundItem = menu.getItem(MenuItems.MENU_SOUND.ordinal());
		soundItem.setTitle(getString(R.string.sound) + (soundOn ? " Off" : " On"));
		return true;
	}
	
	@Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
		switch (MenuItems.values()[item.getItemId()]) {
		case MENU_SOUND:
			switchSound();
			return true;
		}
		
		return super.onMenuItemSelected(featureId, item);
	}
	
	/**
	 * Using onKeyDown instead of onBackPressed to support APIs before level 5
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		// when the back button is pressed, ask the user if they really want
		// to end their game
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			AlertDialog backDialog = (new AlertDialog.Builder(this))
			//.setTitle("Game Over")
			.setMessage(R.string.confirm_quit)
			.setCancelable(false)
			.setPositiveButton("Yes", new DialogInterface.OnClickListener() {				
				@Override
				public void onClick(DialogInterface dialog, int which) {
					MinesweeperFlags.this.finish();
				}
			})
			.setNegativeButton("No", new DialogInterface.OnClickListener() {				
				@Override
				public void onClick(DialogInterface dialog, int which) {
					dialog.dismiss();
				}
			})
			.create();
			backDialog.show();
			return true;
		}
		
		return super.onKeyDown(keyCode, event);
	}
	
	/* Private Helpers */
	/**
	 * Handles all pertinent actions for dealing with flipping a non-flag square
	 * @param position	the index of the clicked square
	 */
	private void handleRegularSquareClicked(int i, int j) {
		if (soundOn) {
			missClip.start();
		}
		flipSquare(i, j);
		adapter.notifyDataSetChanged();
				
		switchTurn();
	}

	/**
	 * Handles all pertinent actions for dealing with flipping a flag square
	 * @param selectedSquare	the square that was clicked
	 */
	private void handleFlagSquareClicked(Square selectedSquare) {
		if (soundOn) {
			foundClip.start();
		}
		selectedSquare.flip();
		incScore();
		mP1ScoreLabel.setText("" + mP1Score);
		mP2ScoreLabel.setText("" + mP2Score);
		adapter.notifyDataSetChanged();
		if ((mP1Score == WIN_SCORE) || (mP2Score == WIN_SCORE)) {
			endGame();
		}
	}
    
	/**
	 * Switches the sound from on to off, or off to on
	 */
    private void switchSound() {
		if (soundOn) {
			soundOn = false;
		} else {
			soundOn = true;
		}
	}

	/**
     * Sets up the board's flags, including positions and number of adjacent flags
     */
    private void initializeFlags() {
    	squares = new Square[SIDE_SZ * SIDE_SZ];
    	for (int i = 0; i < SIDE_SZ * SIDE_SZ; i++) {
    		squares[i] = new RegularSquare();
    	}
    	
    	// determine randomly which squares will have flags
    	HashSet<Integer> flagSet = new HashSet<Integer>();
    	Random rand = new Random();
    	Integer candidate;
    	for (int i = 0; i < NUM_FLAGS; i++) {
    		// choose a candidate at random
    		candidate = new Integer(rand.nextInt(SIDE_SZ * SIDE_SZ));
    		// make sure it has not already been chosen
    		while (flagSet.contains(candidate)) {
    			// if it has, try another number
        		candidate = new Integer(rand.nextInt(SIDE_SZ * SIDE_SZ));
    		}
    		
    		// once we have found a suitable position for the flag, make it a flag square
    		squares[candidate] = new FlagSquare();
    		// and add it to the list
    		flagSet.add(candidate);
    	}
    	
    	// now calculate the number of adjacent flag squares for each non-flag square
    	for (int i = 0; i < SIDE_SZ; i++) {
    		for (int j = 0; j < SIDE_SZ; j++) {
    			// make sure this is a non-flag square
    			if (squares[i*SIDE_SZ + j] instanceof RegularSquare) {
    				calculateNumNeighbors(i, j);
    			}
    		}
    	}
    }
    
    /**
     * Calculates the number of adjacent flag squares to the RegularSquare given
     */
    private void calculateNumNeighbors(int i, int j) {
    	RegularSquare currentSquare = (RegularSquare) squares[i*SIDE_SZ + j];

    	if (validSquare(i, j, AdjacentPositions.TOP_LEFT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.TOP, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.TOP_RIGHT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.RIGHT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM_RIGHT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM_LEFT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    	if (validSquare(i, j, AdjacentPositions.LEFT, true)) {
    		currentSquare.incmNumAdjacent();
    	}
    }
    
    /**
     * Gives control to the other player
     */
    private void switchTurn() {
    	if (mP1Turn) {
    		mP1Turn = false;
    		mP1NameLabel.setTextColor(0xfffafad2);
    		mP1ScoreLabel.setTextColor(0xfffafad2);
    		mP1Bkgd.setBackgroundColor(0xff436eee);
    		mP2NameLabel.setTextColor(0xff778899);
    		mP2ScoreLabel.setTextColor(0xff778899);
    		mP2Bkgd.setBackgroundColor(0xffffec8b);
    	} else {
    		mP1Turn = true;
    		mP1NameLabel.setTextColor(0xff778899);
    		mP1ScoreLabel.setTextColor(0xff778899);
    		mP1Bkgd.setBackgroundColor(0xffffec8b);
    		mP2NameLabel.setTextColor(0xfffafad2);
    		mP2ScoreLabel.setTextColor(0xfffafad2);
    		mP2Bkgd.setBackgroundColor(0xff436eee);
    	}
    }
    
    /**
     * Increments the current player's score
     */
    private void incScore() {
    	if (mP1Turn) {
    		mP1Score++;
    	} else {
    		mP2Score++;
    	}
    }

	private void endGame() {
		if (soundOn) {
			winClip = MediaPlayer.create(this, R.raw.win);
			winClip.setOnCompletionListener(MainMenu.listener);
			winClip.start();
		}
		String winner = (mP1Score == WIN_SCORE) ? "Player 1" : "Player 2";
		AlertDialog winDialog = (new AlertDialog.Builder(this))
								.setTitle("Game Over")
								.setMessage(winner + " wins")
								.setCancelable(false)
								.setNeutralButton("End Game", new DialogInterface.OnClickListener() {				
									@Override
									public void onClick(DialogInterface dialog, int which) {
										MinesweeperFlags.this.finish();
									}
								})
								.create();
		winDialog.show();
	}

	/**
	 * Flips the square at the current position (if it isn't already flipped)
	 * and flips neighbors if this square isn't adjacent to any flag squares
	 * @param i	"i" index of the square
	 * @param j "j" index of the square
	 */
	private void flipSquare(int i, int j) {
		int position = (i * SIDE_SZ) + j;
		Square currentSquare = squares[position];
		
		// if this is a flag square or has already been flipped, do nothing
		if ((currentSquare instanceof FlagSquare) || (currentSquare.isFlipped())) {
			return;
		}
		
		// otherwise, flip it
		currentSquare.flip();
		
		// if this square is adjacent to any flags, end here
		if (((RegularSquare)currentSquare).getmNumAdjacent() != 0) {
			return;
		}
		
		// otherwise, flip all adjacent squares
    	if (validSquare(i, j, AdjacentPositions.TOP_LEFT, false)) {
    		flipSquare(i - 1, j - 1);
    	}
    	if (validSquare(i, j, AdjacentPositions.TOP, false)) {
    		flipSquare(i - 1, j);
    	}
    	if (validSquare(i, j, AdjacentPositions.TOP_RIGHT, false)) {
    		flipSquare(i - 1, j + 1);
    	}
    	if (validSquare(i, j, AdjacentPositions.RIGHT, false)) {
    		flipSquare(i, j + 1);
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM_RIGHT, false)) {
    		flipSquare(i + 1, j + 1);
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM, false)) {
    		flipSquare(i + 1, j);
    	}
    	if (validSquare(i, j, AdjacentPositions.BOTTOM_LEFT, false)) {
    		flipSquare(i + 1, j - 1);
    	}
    	if (validSquare(i, j, AdjacentPositions.LEFT, false)) {
    		flipSquare(i, j - 1);
    	}
	}
	
	/**
	 * Determines if the given coordinates reference a valid square
	 * @param i				"i" index of the square
	 * @param j				"j"	index of the square
	 * @param adjacentPos	the position, in relation to the candidate square, where
	 * 						this square is located
	 * @param mustBeFlag	indicates whether this must be a flag square or not
	 * @return
	 */
	private boolean validSquare(int oldi, int oldj, AdjacentPositions adjacentPos,
			boolean mustBeFlag) {
		int i, j;
		switch (adjacentPos) {
		case TOP_LEFT:
			i = oldi - 1;
			j = oldj - 1;
			break;
		case TOP:
			i = oldi - 1;
			j = oldj;
			break;
		case TOP_RIGHT:
			i = oldi - 1;
			j = oldj + 1;
			break;
		case RIGHT:
			i = oldi;
			j = oldj + 1;
			break;
		case BOTTOM_RIGHT:
			i = oldi + 1;
			j = oldj + 1;
			break;
		case BOTTOM:
			i = oldi + 1;
			j = oldj;
			break;
		case BOTTOM_LEFT:
			i = oldi + 1;
			j = oldj - 1;
			break;
		case LEFT:
			i = oldi;
			j = oldj - 1;
			break;
		default:
			i = oldi;
			j = oldj;
			break;
		}
		
		int index = (i * SIDE_SZ) + j;
		if (!((index >= 0) && (index < SIDE_SZ * SIDE_SZ)
				&& (i >= 0) && (i < SIDE_SZ)
				&& (j >= 0) && (j < SIDE_SZ))) {
			return false;
		}
		if (mustBeFlag && !(squares[index] instanceof FlagSquare)) {
			return false;
		}
		
		return true;
	}
	
	/* Embedded Classes */
	/**
	 * SquareAdapter
	 * Extension of ArrayAdapter to assist with changing cell colors
	 * when squares are flipped
	 */
	class SquareAdapter<T> extends ArrayAdapter<T> {
		
		/**
		 * Constructor
		 */
		SquareAdapter(Context ctx, int resource, T[] items) {
			super(ctx, resource, items);
		}
		
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View cell = convertView;
			
			// if this is not a recycled view, create a new one
			if (cell == null) {
				LayoutInflater inflater = getLayoutInflater();
				cell = inflater.inflate(R.layout.grid_item,	parent, false);
				cell.setTag(cell.findViewById(R.id.list_text));
			}
			
			// set the text for the cell
			TextView text = (TextView) cell.getTag();
			Square cellSquare = squares[position];
			text.setText(cellSquare.toString());
			
			// set the cell to have the correct background color
			if (cellSquare.isFlipped()) {
				text.setBackgroundColor(0xff778899);
			} else {
				text.setBackgroundColor(0xff436eee);
			}
			
			// set the cell to have the correct text color
			if (cellSquare instanceof FlagSquare) {
				text.setTextColor(0xff000000);
			} else {
				text.setTextColor(0xfffafad2);
			}
			
			return cell;
		}
	}
}